package neo.aoi;

import java.util.HashSet;
import java.util.Set;

import org.omg.CosNaming.NamingContextExtPackage.AddressHelper;

import artofillusion.math.BoundingBox;
import artofillusion.math.CoordinateSystem;
import artofillusion.math.Vec3;
import artofillusion.object.CSGObject;
import artofillusion.object.Cube;
import artofillusion.object.Object3D;
import artofillusion.object.ObjectInfo;
import artofillusion.object.TriangleMesh;

/**
 * This class provides a set of functions for converting 3D shapes from a
 * Triangle mesh representation to discrete element representation using volume
 * elements or Voxels.
 * 
 * @author Mauro Santos
 * 
 */
public class TotalSceneVoxelizer {

	/**
	 * Takes a the vertices of a triangle, and divides places it in a voxel grid
	 * of the specified resolution. The coordinates of the voxels which
	 * intersect with the given triangle are returned. This method creates a
	 * voxel at every point where the faces of the generated TriangleMesh
	 * intersect the voxel grid.
	 * 
	 * @param va
	 *            Vertex of the triangle
	 * @param vb
	 *            Vertex of the triangle
	 * @param vc
	 *            Vertex of the triangle
	 * @param res
	 *            Resolution of the voxel grid
	 * @param objectGridCoord
	 *            Object grid coordinates
	 * @return A Set of Vec3 objects which represents the coordinates of the
	 *         produced Voxels.
	 */
	public static Set<Vec3> getVoxels(Vec3 va, Vec3 vb, Vec3 vc, double res,
			Vec3 objectGridCoord) {
		Set<Vec3> voxels = new Vec3Set(res);

		Triangle t = new Triangle(va, vb, vc);
		BoundingBox bounds = t.getBoundingBox();

		// Find Max and mins
		double maxX = bounds.maxx;
		double minX = bounds.minx;
		double maxY = bounds.maxy;
		double minY = bounds.miny;
		double maxZ = bounds.maxz;
		double minZ = bounds.minz;

		// Find ranges to be used - 1 voxel slack
		double lowX = minX - (minX % res) - res;
		double highX = maxX + (res - (maxX % res)) % res + res;
		double lowY = minY - (minY % res) - res;
		double highY = maxY + (res - (maxY % res)) % res + res;
		double lowZ = minZ - (minZ % res) - res;
		double highZ = maxZ + (res - (maxZ % res)) % res + res;

		// Iterate through the grid
		for (double x = lowX; x < highX; x += res) {
			double cx = x;// Voxel center X
			for (double y = lowY; y < highY; y += res) {
				double cy = y;// Voxel center Y
				for (double z = lowZ; z < highZ; z += res) {
					double cz = z;// Voxel center Z

					Voxel v = new Voxel(new Vec3(cx, cy, cz), res);
					if (v.intersects(t)) {
						voxels.add(new Vec3(x + objectGridCoord.x, y
								+ objectGridCoord.y, z + objectGridCoord.z));
					}
				}
			}
		}
		return voxels;
	}

	/**
	 * Converts an Object3D object to its equivalente voxel representation.
	 * Before being converted to voxels, the Object3D is converted to
	 * TriangleMesh using the given tolerance. Voxels are sampled using the
	 * given resolution. This method creates a voxel at every point where the
	 * faces of the generated TriangleMesh intersect the voxel grid.
	 * 
	 * @param obj3D
	 *            The Object3D to be converted to voxels.
	 * @param tol
	 *            The specified tolerance as specified by
	 *            artofillusion.object.Object3D.convertToTriangleMesh(double)
	 * @param res
	 *            Resolution of the voxel grid
	 * @see artofillusion.object.Object3D#convertToTriangleMesh(double)
	 * @return A Set of Vec3 objects which represents the coordinates of the
	 *         produced Voxels.
	 */
	public static Set<Vec3> getVoxels(Object3D obj3D, double tol, double res,
			CoordinateSystem coords) {
		TriangleMesh tmesh = obj3D.convertToTriangleMesh(tol);
		TriangleMesh.Face[] faces = tmesh.getFaces();
		// Mauro:this is just to get the size of the vertex vector!
		Vec3[] vertex = tmesh.getVertexPositions();

		double coordX = coords.getOrigin().x;
		double coordY = coords.getOrigin().y;
		double coordZ = coords.getOrigin().z;

		// Find object coordinates in the grid
		double lowXCoord = coordX - (coordX % res);
		double lowYCoord = coordY - (coordY % res);
		double lowZCoord = coordZ - (coordZ % res);
		double highXCoord = coordX + (res - (coordX % res)) % res;
		double highYCoord = coordY + (res - (coordY % res)) % res;
		double highZCoord = coordZ + (res - (coordZ % res)) % res;

		double xc = lowXCoord;
		double yc = lowYCoord;
		double zc = lowZCoord;
		if (Math.abs((coordX - lowXCoord)) > Math.abs((highXCoord - coordX))) {
			xc = highXCoord;
		} else {
			xc = lowXCoord;
		}
		if (Math.abs((coordY - lowYCoord)) > Math.abs((highYCoord - coordY))) {
			yc = highYCoord;
		} else {
			yc = lowYCoord;
		}
		if (Math.abs((coordZ - lowZCoord)) > Math.abs((highZCoord - coordZ))) {
			zc = highZCoord;
		} else {
			zc = lowZCoord;
		}

		Vec3 objectGridCoord = new Vec3(xc, yc, zc);

		Set<Vec3> voxels = new Vec3Set(res);
		// Convert triangle mesh to voxels
		for (int j = 0; j < faces.length; j++) {
			Set<Vec3> vox_face = getVoxels(vertex[faces[j].v1],
					vertex[faces[j].v2], vertex[faces[j].v3], res,
					objectGridCoord);
			voxels.addAll(vox_face);
		} // For_j

		return voxels;
	}

	/**
	 * Converts the given 3D object to its voxel representation filling the
	 * interior space of the object with voxels too. Before being converted to
	 * voxels, the Object3D is converted to TriangleMesh using the given
	 * tolerance. Voxels are sampled using the given resolution. This method
	 * creates a voxel at each point where object intersects the voxel grid
	 * using constructive solid geometry.
	 * 
	 * @param obj
	 *            the 3D object to be exported.
	 * @param tol
	 *            The specified tolerance as specified by
	 *            artofillusion.object.Object3D.convertToTriangleMesh(double)
	 *            Resolution of the voxel grid
	 * @see artofillusion.object.Object3D#convertToTriangleMesh(double)
	 * @return A Set of Vec3 objects which represents the coordinates of the
	 *         produced Voxels.
	 */
	public static Set<Vec3> getVoxelsWithFillingSet(Object3D obj,
			double tol, double res, CoordinateSystem coords) {
		Set<Vec3> bbList = new HashSet<Vec3>();
		BoundingBox bounds = obj.getBounds();
		double deltaX = bounds.minx + (bounds.maxx - bounds.minx) / 2;
		double deltaY = bounds.miny + (bounds.maxy - bounds.miny) / 2;
		double deltaZ = bounds.minz + (bounds.maxz - bounds.minz) / 2;

		ObjectInfo objinfo = new ObjectInfo(obj, new CoordinateSystem(new Vec3(
				-deltaX, -deltaY, -deltaZ), 0, 0, 0), "Objeto");
		Object3D ip = new Cube(1.1 * (bounds.maxx - bounds.minx),
				1.1 * (bounds.maxy - bounds.miny), res); // Intersection
		// Plane

		for (double z = bounds.minz; z <= bounds.maxz; z += res) {
			ObjectInfo ip_info = new ObjectInfo(ip, new CoordinateSystem(
					new Vec3(0, 0, z), 0, 0, 0), "cubo");

			CSGObject intersection = new CSGObject(objinfo, ip_info,
					CSGObject.INTERSECTION);
			//bbList.add(intersection.convertToTriangleMesh(tol).getVertexPositions());

		}

		return bbList;
	}
	
	
	public static Set<BoundingBox> getVoxelsWithFilling(Object3D obj,
			double tol, double res, CoordinateSystem coords) {
		Set<BoundingBox> bbList = new HashSet<BoundingBox>();
		BoundingBox bounds = obj.getBounds();
		double deltaX = bounds.minx + (bounds.maxx - bounds.minx) / 2;
		double deltaY = bounds.miny + (bounds.maxy - bounds.miny) / 2;
		double deltaZ = bounds.minz + (bounds.maxz - bounds.minz) / 2;

		ObjectInfo objinfo = new ObjectInfo(obj, new CoordinateSystem(new Vec3(
				-deltaX, -deltaY, -deltaZ), 0, 0, 0), "Objeto");
		Object3D ip = new Cube(1.1 * (bounds.maxx - bounds.minx),
				1.1 * (bounds.maxy - bounds.miny), res); // Intersection
		// Plane

		for (double z = bounds.minz; z <= bounds.maxz; z += res) {
			ObjectInfo ip_info = new ObjectInfo(ip, new CoordinateSystem(
					new Vec3(0, 0, z), 0, 0, 0), "cubo");

			CSGObject intersection = new CSGObject(objinfo, ip_info,
					CSGObject.INTERSECTION);
			bbList.add(intersection.convertToTriangleMesh(tol).getBounds());

		}

		return bbList;
	}
}
